#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#include <psp2/display.h>
#include <psp2/io/fcntl.h>

#include "retro.h"
#include "utils.h"
#include "file.h"
#include "video.h"
#include "audio.h"
#include "input.h"
#include "config.h"
#include "browser.h"
#include "ui.h"
#include "states.h"
#include "menu.h"
#include "config.h"
#include "lang.h"
#include "libretro.h"
#include "boot.h"
#include "text.h"

#define CORE_OPTIONS_VERSION 2

static void cleanupCoreEntries();
static int updateOptionDisplay(const struct retro_core_option_display *option_display);
static int setCoreEntriesFromVariables(void *in_varialbes);
static int setCoreEntriesFromOptions(void *in_options, int version);

struct retro_system_info core_system_info;
struct retro_system_av_info core_system_av_info;
static struct retro_core_options_update_display_callback core_update_display_cb;

extern enum SceGxmTextureFormat video_texture_format;

char core_assets_dir[MAX_PATH_LENGTH];
char core_system_dir[MAX_PATH_LENGTH];

static unsigned core_option_language = RETRO_LANGUAGE_CHINESE_SIMPLIFIED;
static int core_device = RETRO_DEVICE_JOYPAD;

static char **valid_exts = NULL;
static void *rom_data = NULL;

static int retro_initialized = 0;
int game_loading = 0, game_loaded = 0, game_run = 0;

static int game_reset = 0, game_exit = 0, game_save_state = 0, game_load_state = 0;
static int game_changed = 0;

static int core_varialbe_need_update = 0;
static int n_core_options = 0;

int retro_input_bitmasks = 0;
int retro_input_polled = 0;

static int makeCoreAssetsDirPath(char *path)
{
    snprintf(path, MAX_PATH_LENGTH, "%s/%s", APP_DATA_DIR, CORE_ASSETS_DIR_NAME);

    return 0;
}

static int makeCoreSystemDirPath(char *path)
{
#if defined(USE_INTE_SYSTEM_DIR)
    if (private_assets_dir)
        snprintf(path, MAX_PATH_LENGTH, "%s/%s", private_assets_dir, CORE_SYSTEM_DIR_NAME);
    else
        snprintf(path, MAX_PATH_LENGTH, "%s/%s", public_assets_dir, CORE_SYSTEM_DIR_NAME);
#else
    snprintf(path, MAX_PATH_LENGTH, "%s/%s", APP_DATA_DIR, CORE_SYSTEM_DIR_NAME);
#endif

    return 0;
}

static int makeSrmPath(char *path)
{
    char name[MAX_NAME_LENGTH];
    makeCurFileName(name);
    char base_name[MAX_NAME_LENGTH];
    makeBaseName(base_name, name, MAX_NAME_LENGTH);
    snprintf(path, MAX_PATH_LENGTH, "%s/%s.srm", (CORE_SAVEFILES_DIR), base_name);

    return 0;
}

#if !defined(USE_INTE_SYSTEM_DIR)
#if defined(PS_BUILD)
static void copyBios(const char *name)
{
    char src_path[MAX_PATH_LENGTH];
    if (private_assets_dir)
        snprintf(src_path, MAX_PATH_LENGTH, "%s/%s/%s", private_assets_dir, CORE_SYSTEM_DIR_NAME, name);
    else
        snprintf(src_path, MAX_PATH_LENGTH, "%s/%s/%s", public_assets_dir, CORE_SYSTEM_DIR_NAME, name);

    char dst_path[MAX_PATH_LENGTH];
    snprintf(dst_path, MAX_PATH_LENGTH, "%s/%s", core_system_dir, name);
    
    if (checkFileExist(src_path) && !checkFileExist(dst_path))
    {
        createFolder(core_system_dir);
        copyFile(src_path, dst_path, NULL);
    }
}
#endif
#endif

void setCoreOptionsNum(int num)
{
    n_core_options = num;
}

int getCoreOptionsNum()
{
    return n_core_options;
}

int isValidFile(char *path)
{
    if (!valid_exts)
        return 0;

    char *ext = strrchr(path, '.');
    if (!ext)
        return 0;

    int i = 0;
    while (valid_exts[i])
    {
        if (strcasecmp(ext + 1, valid_exts[i]) == 0)
            return 1;
        i++;
    }

    return 0;
}

static void cleanupValidExts()
{
    if (valid_exts)
    {
        int i = 0;
        while (valid_exts[i])
        {
            free(valid_exts[i]);
            i++;
        }
        free(valid_exts);
        valid_exts = NULL;
    }
}

static int getValidExts()
{
    int ret = 0;

    if (!core_system_info.valid_extensions)
        return -1;
    APP_LOG("valid_extensions: %s\n", core_system_info.valid_extensions);

    int exts_len = strlen(core_system_info.valid_extensions);
    if (exts_len == 0)
        return -1;

    char *exts = (char *)malloc(exts_len + 1);
    if (!exts)
        return -1;
    strcpy(exts, core_system_info.valid_extensions);

    int n_exts = 0;
    int i;
    for (i = 0; i < exts_len + 1; i++)
    {
        if (exts[i] == '|' || exts[i] == '\0')
            n_exts++;
    }
    // APP_LOG("n_exts: %d\n", n_exts);

    if (valid_exts)
        cleanupValidExts();
    valid_exts = (char **)calloc((n_exts + 1), sizeof(char *));
    if (!valid_exts)
    {
        free(exts);
        return -1;
    }

    int j = 0;
    char ch;
    char *p = exts;
    for (i = 0; i < exts_len + 1; i++)
    {
        if (exts[i] == '|' || exts[i] == '\0')
        {
            ch = exts[i];
            exts[i] = '\0';
            valid_exts[j] = (char *)malloc(strlen(p) + 1);
            if (!valid_exts[j])
            {
                ret = -2;
                break;
            }
            strcpy(valid_exts[j], p);
            exts[i] = ch;
            p = &exts[i] + 1;
            j++;
        }
    }
    valid_exts[j] = NULL;
    free(exts);

    // for (i = 0; i < j + 1; i++)
    // APP_LOG("exts[%d]: %s\n", i, valid_exts[i]);

    return ret;
}

static void retroLogCallback(enum retro_log_level level, const char *fmt, ...)
{
    int enable_loading_log = (setting_config.loading_log && game_loading);

    if (!setting_config.core_log && !enable_loading_log)
        return;

    va_list list;
    char string[512];

    va_start(list, fmt);
    vsprintf(string, fmt, list);
    va_end(list);

#ifdef DEBUG
    printf(string);
#endif

    if (enable_loading_log)
        textListAddEntryFromString(getDefaultTextList(), string);

    if (!setting_config.core_log)
        return;

    SceUID fd = sceIoOpen((CORE_LOG_PATH), SCE_O_WRONLY | SCE_O_CREAT | SCE_O_APPEND, 0777);
    if (fd < 0)
        return;

    sceIoWrite(fd, string, strlen(string));
    sceIoClose(fd);
}

void setRetroVariableUpdate(int update)
{
    core_varialbe_need_update = update;
}

static void updateRetroVariables(struct retro_variable *var)
{
    int i;
    CoreOptionValue *option;
    for (i = 0; i < n_core_options; i++)
    {
        option = core_entries[i].option;
        if ((strcmp(var->key, option->key) == 0))
        {
            var->value = option->values[option->pos];
            return;
        }
    }
}

void updateCoreOptionsDisplayCallback()
{
    if (core_update_display_cb.callback)
        core_update_display_cb.callback();
}

static bool retroEnvironmentCallback(unsigned int cmd, void *data)
{
    if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE)
    {
        *(bool *)data = core_varialbe_need_update;
        if (core_varialbe_need_update)
        {
            APP_LOG("case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: %d\n", core_varialbe_need_update);
            core_varialbe_need_update = 0;
        }

        return true;
    }

    switch (cmd)
    {
    case RETRO_ENVIRONMENT_GET_CAN_DUPE:
    {
        APP_LOG("case RETRO_ENVIRONMENT_GET_CAN_DUPE: 1\n");
        *(bool *)data = true;
    }
    break;

    case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
    {
        enum retro_pixel_format pix_fmt = *(const enum retro_pixel_format *)data;
        enum SceGxmTextureFormat texture_format = video_texture_format;
        switch (pix_fmt)
        {
        case RETRO_PIXEL_FORMAT_0RGB1555:
        {
            texture_format = SCE_GXM_TEXTURE_FORMAT_X1U5U5U5_1RGB;
            APP_LOG("case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: RETRO_PIXEL_FORMAT_0RGB1555\n");
        }
        break;

        case RETRO_PIXEL_FORMAT_RGB565:
        {
            texture_format = SCE_GXM_TEXTURE_FORMAT_U5U6U5_RGB;
            APP_LOG("case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: RETRO_PIXEL_FORMAT_RGB565\n");
        }
        break;

        case RETRO_PIXEL_FORMAT_XRGB8888:
        {
            texture_format = SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1RGB;
            APP_LOG("case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: RETRO_PIXEL_FORMAT_XRGB8888\n");
        }
        break;

        default:
        {
            APP_LOG("case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: %d is unknow!\n", pix_fmt);
            return false;
        }
        }
        if (texture_format != video_texture_format)
        {
            APP_LOG("pixel_format is changed, video will refresh!\n");
            video_texture_format = texture_format;
            vitaVideoSetVideoNeedRefresh(1);
        }
    }
    break;

    case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
    {
        *(const char **)data = core_assets_dir;
        createFolder(core_assets_dir);
    }
    break;

    case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
    {
        *(const char **)data = core_system_dir;
        createFolder(core_system_dir);
    }
    break;

    case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
    {
        *(const char **)data = CORE_SAVEFILES_DIR;
        createFolder(CORE_SAVEFILES_DIR);
    }
    break;

    case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
    {
        APP_LOG("case RETRO_ENVIRONMENT_GET_LOG_INTERFACE\n");
        struct retro_log_callback *cb = (struct retro_log_callback *)data;
        cb->log = retroLogCallback;
    }
    break;

    case RETRO_ENVIRONMENT_GET_LANGUAGE:
    {
        // APP_LOG("case RETRO_ENVIRONMENT_GET_LANGUAGE: \"%u\".\n", core_option_language);
        *(unsigned *)data = core_option_language;
    }
    break;

    case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION:
    {
        // APP_LOG("case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE\n");
        *(unsigned *)data = CORE_OPTIONS_VERSION;
    }
    break;

    case RETRO_ENVIRONMENT_GET_VARIABLE:
    {
        // APP_LOG("case RETRO_ENVIRONMENT_GET_VARIABLE\n");
        if (!core_entries)
            return false;
        struct retro_variable *var = (struct retro_variable *)data;
        updateRetroVariables(var);
    }
    break;

    case RETRO_ENVIRONMENT_SET_VARIABLES:
    {
        APP_LOG("case RETRO_ENVIRONMENT_SET_VARIABLES\n");
        APP_LOG("setCoreEntriesFromVariables...\n");
        int ret = setCoreEntriesFromVariables(data);
        if (ret < 0)
            APP_LOG("setCoreEntriesFromVariables failed\n");
        else
            APP_LOG("setCoreEntriesFromVariables done\n");
    }
    break;

    case RETRO_ENVIRONMENT_SET_CORE_OPTIONS:
    {
        APP_LOG("case RETRO_ENVIRONMENT_SET_CORE_OPTIONS\n");
        APP_LOG("setCoreEntriesFromOptions...\n");
        int ret = setCoreEntriesFromOptions(data, RETRO_ENVIRONMENT_SET_CORE_OPTIONS);
        if (ret < 0)
            APP_LOG("setCoreEntriesFromOptions failed\n");
        else
            APP_LOG("setCoreEntriesFromOptions done\n");
    }
    break;

    case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL:
    {
        APP_LOG("case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL\n");
        APP_LOG("setCoreEntriesFromOptions...\n");
        int ret = setCoreEntriesFromOptions(data, RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL);
        if (ret < 0)
            APP_LOG("setCoreEntriesFromOptions failed\n");
        else
            APP_LOG("setCoreEntriesFromOptions done\n");
    }
    break;

    case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2:
    {
        APP_LOG("case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2\n");
        APP_LOG("setCoreEntriesFromOptions...\n");
        int ret = setCoreEntriesFromOptions(data, RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2);
        if (ret < 0)
            APP_LOG("setCoreEntriesFromOptions failed\n");
        else
            APP_LOG("setCoreEntriesFromOptions done\n");

        return false;
    }
    break;

    case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL:
    {
        APP_LOG("case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL\n");
        APP_LOG("setCoreEntriesFromOptions...\n");
        int ret = setCoreEntriesFromOptions(data, RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL);
        if (ret < 0)
            APP_LOG("setCoreEntriesFromOptions failed\n");
        else
            APP_LOG("setCoreEntriesFromOptions done\n");

        return false;
    }
    break;

    case RETRO_ENVIRONMENT_GET_INPUT_BITMASKS:
    {
        retro_input_bitmasks = 1;
        APP_LOG("case RETRO_ENVIRONMENT_GET_INPUT_BITMASKS: %d\n", retro_input_bitmasks);
    }
    break;

    case RETRO_ENVIRONMENT_SET_GEOMETRY:
    {
        APP_LOG("case RETRO_ENVIRONMENT_SET_GEOMETRY\n");
        struct retro_system_av_info *av_info = &core_system_av_info;
        struct retro_game_geometry *geom = (struct retro_game_geometry *)&av_info->geometry;
        const struct retro_game_geometry *in_geom = (const struct retro_game_geometry *)data;
        if (!geom)
            return false;
        if (in_geom)
        {
            memcpy(geom, in_geom, sizeof(struct retro_game_geometry));
            vitaVideoSetVideoNeedRefresh(1);
        }
    }
    break;

    case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO:
    {
        APP_LOG("case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO\n");
        struct retro_system_av_info *av_info = (struct retro_system_av_info *)data;
        uint32_t new_sample_rate = (uint32_t)av_info->timing.sample_rate;
        uint32_t old_sample_rate = (uint32_t)core_system_av_info.timing.sample_rate;
        memcpy(&core_system_av_info, av_info, sizeof(struct retro_system_av_info));
        if ((new_sample_rate > 0) && (new_sample_rate != old_sample_rate))
        {
            APP_LOG("new sample_rate %d\n", new_sample_rate);
            APP_LOG("old sample_rate %d\n", old_sample_rate);
            APP_LOG("frequency need reset\n");
            vitaAudioResetFrequency(new_sample_rate);
        }
        vitaVideoSetVideoNeedRefresh(1);
    }
    break;

    case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE:
    {
        // APP_LOG("case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE\n");
        int result = 1 | 2;
        if (data)
            *(int *)data = result;
    }
    break;

    case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK:
    {
        APP_LOG("case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK\n");
        const struct retro_core_options_update_display_callback
            *update_display_cb =
                (const struct retro_core_options_update_display_callback *)data;

        if (update_display_cb && update_display_cb->callback)
            core_update_display_cb.callback = update_display_cb->callback;
        else
            core_update_display_cb.callback = NULL;
    }
    break;

    case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY:
    {
        APP_LOG("case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY\n");
        const struct retro_core_option_display
            *option_display =
                (const struct retro_core_option_display *)data;

        if (option_display && option_display->key)
            updateOptionDisplay(option_display);
    }
    break;

    default:
    {
        // printf("[Environ]: UNSUPPORTED (#%u).\n", cmd);
        return false;
    }
    }

    return true;
}

static void setRetroCallbacks()
{
    retro_set_environment(&retroEnvironmentCallback);
    retro_set_video_refresh(&retroVideoRefreshCallback);
    retro_set_audio_sample_batch(&retroAudioSampleBatchCallback);
    retro_set_input_poll(&retroInputPollCallback);
    retro_set_input_state(&retroInputStateCallback);
}

static void setRetroControllerPortDevice()
{
    int i;
    for (i = 0; i < 4; i++)
        retro_set_controller_port_device(i, core_device);
}

static int saveSrm()
{
    uint64_t size = retro_get_memory_size(RETRO_MEMORY_SAVE_RAM);
    if (size <= 0)
        return -1;

    uint8_t *data = retro_get_memory_data(RETRO_MEMORY_SAVE_RAM);
    if (!data)
        return -1;

    char path[MAX_PATH_LENGTH];
    makeSrmPath(path);
    createFolder(CORE_SAVEFILES_DIR);
    int fd = sceIoOpen(path, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777);
    if (fd < 0)
        return fd;

    sceIoWrite(fd, data, size);
    sceIoClose(fd);

    return 0;
}

static int loadSrm()
{
    uint64_t sram_size = retro_get_memory_size(RETRO_MEMORY_SAVE_RAM);
    if (sram_size <= 0)
        return -1;

    uint8_t *sram_data = retro_get_memory_data(RETRO_MEMORY_SAVE_RAM);
    if (!sram_data)
        return -1;

    char path[MAX_PATH_LENGTH];
    makeSrmPath(path);
    int fd = sceIoOpen(path, SCE_O_RDONLY, 0);
    if (fd < 0)
        return fd;

    uint64_t size = sceIoLseek(fd, 0, SCE_SEEK_END);
    if (size <= 0 || size > sram_size)
    {
        sceIoClose(fd);
        return -1;
    }

    char *data = (char *)malloc(size);
    if (!data)
    {
        sceIoClose(fd);
        return -1;
    }

    sceIoLseek(fd, 0, SCE_SEEK_SET);

    char *buf = data;
    while (1)
    {
        int read_size = sceIoRead(fd, buf, TRANSFER_SIZE);
        if (read_size < 0)
        {
            free(data);
            sceIoClose(fd);
            return -1;
        }

        if (read_size == 0)
            break;

        buf += read_size;
    }
    sceIoClose(fd);

    memcpy(sram_data, data, size);
    free(data);

    return 0;
}

static int loadGameFromFile(const char *path)
{
    int ret;
    struct retro_game_info game_info;

    if (rom_data)
    {
        free(rom_data);
        rom_data = NULL;
    }

    game_info.path = path;
    game_info.data = NULL;
    game_info.size = 0;
    game_info.meta = NULL;

    ret = retro_load_game(&game_info);

    return ret;
}

static int loadGameFromMemory(const char *path)
{
    int ret = 0;

    SceUID fd = sceIoOpen(path, SCE_O_RDONLY, 0);
    if (fd < 0)
        return 0;

    uint64_t size = sceIoLseek(fd, 0, SCE_SEEK_END);
    if (size <= 0)
    {
        sceIoClose(fd);
        return 0;
    }

    struct retro_game_info game_info;

    if (rom_data)
    {
        free(rom_data);
        rom_data = NULL;
    }

    rom_data = (void *)malloc(size);
    if (!rom_data)
    {
        sceIoClose(fd);
        return 0;
    }

    sceIoLseek(fd, 0, SCE_SEEK_SET);

    uint8_t *buf = (uint8_t *)rom_data;
    while (1)
    {
        int read = sceIoRead(fd, buf, TRANSFER_SIZE);
        if (read < 0)
        {
            free(rom_data);
            rom_data = NULL;
            sceIoClose(fd);
            return 0;
        }

        if (read == 0)
            break;

        buf += read;
    }
    sceIoClose(fd);

    game_info.path = path;
    game_info.data = rom_data;
    game_info.size = size;
    game_info.meta = NULL;

    ret = retro_load_game(&game_info);
    if (!ret)
    {
        free(rom_data);
        rom_data = NULL;
    }

    return ret;
}

int loadGame(const char *path)
{
    int ret;

    APP_LOG("load rom...\n");
    APP_LOG("rom path: %s\n", path);

    printSplash();

    textViewSetAutoScroll(1);
    if (setting_config.loading_log)
        initTextViewThread();

    game_loading = 1;
    loadGraphicsConfig(CONFIG_TYPE_GAME);
    loadControlConfig(CONFIG_TYPE_GAME);
    loadMiscConfig(CONFIG_TYPE_GAME);
    loadCoreConfig(CONFIG_TYPE_GAME);

    if (!retro_initialized)
    {
        retro_init();
        retro_initialized = 1;
        APP_LOG("retro_init\n");
    }

    APP_LOG("need_fullpath: %d\n", core_system_info.need_fullpath);
    if (core_system_info.need_fullpath)
        ret = loadGameFromFile(path);
    else
        ret = loadGameFromMemory(path);

    game_loading = 0;
    textViewSetAutoScroll(0);

    if (!ret)
    {
        exitGame();
        APP_LOG("load rom failed\n");
        if (setting_config.loading_log)
            waitTextViewThreadEnd();
        return ret;
    }
    WriteFile((LASTFILE_PATH), path, strlen(path) + 1);
    if (setting_config.loading_log)
        finishTextViewThread();

    game_loaded = 1;

    updateCoreOptionsDisplayCallback();

    setRetroControllerPortDevice();
    retro_get_system_av_info(&core_system_av_info);
    loadSrm();
    retro_run();

    vitaAudioInit();
    vitaVideoInit();
    vitaAudioResume();
    vitaVideoResume();

    lockQuickMenu();

    resetPad();
    setMenuNeedRefresh(1);
    vitaVideoSetVideoNeedRefresh(1);
    retro_input_polled = 0;
    game_run = 1;

    APP_LOG("load rom done\n");

    return ret;
}

int unloadGame()
{
    pauseGame();

    if (game_loaded)
    {
        saveSrm();
        retro_unload_game();
        game_loaded = 0;
        vitaAudioShutdown();
        vitaVideoShutdown();
    }

    if (rom_data)
    {
        free(rom_data);
        rom_data = NULL;
    }

    if (retro_initialized)
    {
        retro_deinit();
        retro_initialized = 0;
        APP_LOG("retro_deinit\n");
    }

    return 0;
}

int pauseGame()
{
    if (game_run)
    {
        game_run = 0;
        vitaAudioPause();
        vitaVideoPause();
    }

    return 0;
}

int resumeGame()
{
    if (game_loaded)
    {
        game_run = 1;
        vitaAudioResume();
        vitaVideoResume();
    }

    return 0;
}

int resetGame()
{
    pauseGame();
    retro_reset();
    vitaAudioResetBuffers();
    resumeGame();

    return 0;
}

int reloadGame()
{
    pauseGame();
    unloadGame();
    char path[MAX_PATH_LENGTH];
    makeCurFilePath(path);
    if (!loadGame(path))
        return -1;

    return 0;
}

int exitGame()
{
    pauseGame();
    if (game_loaded)
    {
        if (misc_config.auto_save_load && retro_input_polled)
        {
            saveState(-1);
            unloadPreview();
        }
        unloadGame();
        setMenuNeedRefresh(1);
    }

    loadGraphicsConfig(CONFIG_TYPE_MAIN);
    loadControlConfig(CONFIG_TYPE_MAIN);
    loadMiscConfig(CONFIG_TYPE_MAIN);

    if (!isMainCoreEntiesEnable())
    {
        if (core_entries)
            cleanupCoreEntries();
    }
    else
    {
        loadCoreConfig(CONFIG_TYPE_MAIN);
        updateCoreOptionsDisplayCallback();
    }

    return 0;
}

int resetGameInRunning()
{
    if (game_run)
    {
        game_reset = 1;
        game_changed = 1;
    }

    return 0;
}

int exitGameInRunning()
{
    if (game_run)
    {
        game_exit = 1;
        game_changed = 1;
    }

    return 0;
}

int saveStateInRunning()
{
    if (game_run)
    {
        game_save_state = 1;
        game_changed = 1;
    }

    return 0;
}

int loadStateInRunning()
{
    if (game_run)
    {
        game_load_state = 1;
        game_changed = 1;
    }

    return 0;
}

int runGame()
{
    retroInputPoll();
    retro_run();

    if (game_changed)
    {
        game_changed = 0;
        if (game_reset)
        {
            game_reset = 0;
            resetGame();
        }
        else if (game_exit)
        {
            game_exit = 0;
            exitGame();
            if (exec_boot_mode == BOOT_MODE_GAME)
                restoreBootParams();
        }
        else if (game_save_state)
        {
            game_save_state = 0;
            pauseGame();
            saveState(getStatesFocusPos());
            resumeGame();
        }
        else if (game_load_state)
        {
            game_load_state = 0;
            pauseGame();
            loadState(getStatesFocusPos());
            resumeGame();
        }
        return 0;
    }

    return 0;
}

int initRetro()
{
    APP_LOG("initRetro...\n");

    makeCoreAssetsDirPath(core_assets_dir);
    makeCoreSystemDirPath(core_system_dir);
    core_update_display_cb.callback = NULL;
    core_option_language = RETRO_LANGUAGE_CHINESE_SIMPLIFIED;
    core_device = RETRO_DEVICE_JOYPAD;
    setRetroCallbacks();
    retro_get_system_info(&core_system_info);
    getValidExts();
#if !defined(USE_INTE_SYSTEM_DIR)
#if defined(PS_BUILD)
    copyBios("PSXONPSP660.BIN");
#endif
#endif

    APP_LOG("initRetro done\n");

    return 0;
}

int finishRetro()
{
    if (retro_initialized)
    {
        retro_deinit();
        retro_initialized = 0;
    }

    return 0;
}

static void cleanupCoreEntries()
{
    if (!core_entries)
        return;

    // APP_LOG("cleanupCoreEntries...\n");
    int i, j;
    for (i = 0; i < getCoreEntiesLength(); i++)
    {
        if (core_entries[i].type != MENU_ENTRY_TYPE_OPTION_CORE)
            continue;

        if (core_entries[i].desc)
            free(core_entries[i].desc);

        CoreOptionValue *option = core_entries[i].option;
        if (!option)
            continue;

        if (option->key)
            free(option->key);
        if (option->default_value)
            free(option->default_value);
        if (option->values)
        {
            for (j = 0; j < option->n_options; j++)
            {
                if (option->values[j])
                    free(option->values[j]);
            }

            free(option->values);
        }
        if (option->labels)
        {
            for (j = 0; j < option->n_options; j++)
            {
                if (option->labels[j])
                    free(option->labels[j]);
            }

            free(option->labels);
        }
        free(option);
    }
    free(core_entries);
    core_entries = NULL;

    setCoreEntiesInfo(NULL, 0, 0);
    // APP_LOG("cleanupCoreEntries done\n");
}

static int updateOptionDisplay(const struct retro_core_option_display *option_display)
{
    if (!core_entries || !option_display)
        return -1;

    // printf("option_display: %s\n", option_display->key);

    int i;
    CoreOptionValue *option;
    for (i = 0; i < n_core_options; i++)
    {
        option = core_entries[i].option;
        if (strcmp(option_display->key, option->key) == 0)
        {
            core_entries[i].enable = option_display->visible;
            break;
        }
    }

    if (getCurrentMenuEntries() == core_entries)
        refreshMenuEntriesInfo();

    return 0;
}

static int setCoreEntriesFromVariables(void *in_varialbes)
{
    if (!in_varialbes)
        return -1;

    if (core_entries)
        cleanupCoreEntries();

    struct retro_variable *varialbes = (struct retro_variable *)in_varialbes;

    int n_options = 0;
    while (varialbes[n_options].key)
    {
        n_options++;
    }
    // printf("n_options: %d", n_options);

    if (n_options == 0)
        return -1;

    core_entries = (MenuEntry *)calloc((n_options + 1), sizeof(MenuEntry));
    if (!core_entries)
        return -1;

    setCoreOptionsNum(n_options);
    setCoreEntiesInfo(core_entries, n_options + 1, 1);

    int i;
    char *desc_values = NULL;
    for (i = 0; i < n_options; i++)
    {
        core_entries[i].type = MENU_ENTRY_TYPE_OPTION_CORE;

        CoreOptionValue *core_option = (CoreOptionValue *)calloc(1, sizeof(CoreOptionValue));
        if (!core_option)
            continue;
        core_entries[i].option = (void *)core_option;

        if (varialbes[i].key)
        {
            core_option->key = (char *)calloc((strlen(varialbes[i].key) + 1), sizeof(char));
            if (core_option->key)
                strcpy(core_option->key, varialbes[i].key);
            // printf("core_option->key: %s\n", core_option->key);
        }

        if (!varialbes[i].value)
            continue;

        if (desc_values)
            free(desc_values);
        desc_values = (char *)calloc((strlen(varialbes[i].value) + 1), sizeof(char));
        if (!desc_values)
            continue;
        strcpy(desc_values, varialbes[i].value);
        // printf("desc_values: %s\n", desc_values);

        char *values = desc_values;
        char *p = strchr(desc_values, ';');
        if (p)
        {
            int desc_len = p - desc_values;
            core_entries[i].desc = (char *)calloc(desc_len + 1, sizeof(char));
            if (core_entries[i].desc)
                strncpy(core_entries[i].desc, desc_values, desc_len);
            // printf("core_entries[%d].desc: %s\n", i, core_entries[i].desc);
            values = p + 1;
        }

        while (*values == ' ' || *values == '\t')
            values++;
        trimString(values);

        // printf("values: %s\n", values);
        int values_len = strlen(values);
        if (values_len <= 0)
            continue;

        int j;
        int n_values = 0;
        for (j = 0; j < values_len + 1; j++)
        {
            if (values[j] == '|' || values[j] == '\0')
                n_values++;
        }
        if (n_values == 0)
            continue;
        // printf("n_values: %d\n", n_values);

        core_option->n_options = n_values;
        core_option->pos = 0;
        core_option->values = (char **)calloc(n_values, sizeof(char *));
        if (!core_option->values)
            continue;

        int k = 0;
        char *p1 = values;
        char *p2 = values;
        for (j = 0; j < values_len + 1; j++)
        {
            if (*p2 == '|' || *p2 == '\0')
            {
                int value_len = p2 - p1;
                core_option->values[k] = (char *)calloc((value_len + 1), sizeof(char));
                if (core_option->values[k])
                    strncpy(core_option->values[k], p1, value_len);
                // printf("core_option->values[%d]: %s\n", k, core_option->values[k]);
                p1 = p2 + 1;
                k++;
            }
            p2++;
        }
        char *default_value = core_option->values[0];
        core_option->default_value = (char *)calloc((strlen(default_value) + 1), sizeof(char));
        if (core_option->default_value)
            strcpy(core_option->default_value, default_value);

        core_entries[i].enable = 1;
    }

    if (desc_values)
        free(desc_values);

    core_entries[n_options].desc = (char *)calloc((strlen(STR_RESET_DEFAULT_CONFIG) + 1), sizeof(char));
    if (core_entries[n_options].desc)
        strcpy(core_entries[n_options].desc, STR_RESET_DEFAULT_CONFIG);
    core_entries[n_options].type = MENU_ENTRY_TYPE_CALLBACK;
    core_entries[n_options].option = resetCoreConfig;
    core_entries[n_options].enable = 1;

    if (game_loading || game_loaded)
        loadCoreConfig(CONFIG_TYPE_GAME);
    else
        loadCoreConfig(CONFIG_TYPE_MAIN);

    return 0;
}

static int setCoreEntriesFromOptions(void *in_options, int type)
{
    if (!in_options)
        return -1;

    if (core_entries)
        cleanupCoreEntries();

    int n_options = 0;
    void *option_definition = NULL;
    int option_version = 0;
    if (type == RETRO_ENVIRONMENT_SET_CORE_OPTIONS || type == RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL)
    {
        option_version = 1;
        struct retro_core_option_definition *option_v1_definition = NULL;
        if (type == RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL)
        {
            struct retro_core_options_intl *options_v1_intl = (struct retro_core_options_intl *)in_options;
            if (options_v1_intl->local)
                option_v1_definition = options_v1_intl->local;
            else if (options_v1_intl->us)
                option_v1_definition = options_v1_intl->us;
        }
        else
        {
            option_v1_definition = (struct retro_core_option_definition *)in_options;
        }

        if (!option_v1_definition)
            return -1;

        while (option_v1_definition[n_options].key)
        {
            n_options++;
        }

        option_definition = option_v1_definition;
    }
    else if (type == RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 || type == RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL)
    {
        option_version = 2;
        struct retro_core_option_v2_definition *option_v2_definition = NULL;
        if (type == RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL)
        {
            struct retro_core_options_v2_intl *options_v2_intl = (struct retro_core_options_v2_intl *)in_options;
            if (options_v2_intl->local && options_v2_intl->local->definitions)
                option_v2_definition = options_v2_intl->local->definitions;
            else if (options_v2_intl->us && options_v2_intl->us->definitions)
                option_v2_definition = options_v2_intl->us->definitions;
        }
        else
        {
            struct retro_core_options_v2 *options_v2 = (struct retro_core_options_v2 *)in_options;
            option_v2_definition = options_v2->definitions;
        }

        if (!option_v2_definition)
            return -1;

        while (option_v2_definition[n_options].key)
        {
            n_options++;
        }

        option_definition = option_v2_definition;
    }
    else
    {
        return -1;
    }

    if (n_options == 0)
        return -1;

    core_entries = (MenuEntry *)calloc((n_options + 1), sizeof(MenuEntry));
    if (!core_entries)
        return -1;

    setCoreOptionsNum(n_options);
    setCoreEntiesInfo(core_entries, n_options + 1, 1);

    int i, j;
    for (i = 0; i < n_options; i++)
    {
        core_entries[i].type = MENU_ENTRY_TYPE_OPTION_CORE;

        const char *key;
        const char *desc;
        const char *default_value;
        struct retro_core_option_value *values;

        if (option_version == 1)
        {
            struct retro_core_option_definition *options = (struct retro_core_option_definition *)option_definition;
            key = options[i].key;
            desc = options[i].desc;
            default_value = options[i].default_value;
            values = options[i].values;
        }
        else
        {
            struct retro_core_option_v2_definition *options = (struct retro_core_option_v2_definition *)option_definition;
            key = options[i].key;
            desc = options[i].desc;
            default_value = options[i].default_value;
            values = options[i].values;
        }

        if (desc)
        {
            // printf("core_entries: desc: %s\n", desc);
            core_entries[i].desc = (char *)calloc((strlen(desc) + 1), sizeof(char));
            if (core_entries[i].desc)
                strcpy(core_entries[i].desc, desc);
        }

        CoreOptionValue *core_option = (CoreOptionValue *)calloc(1, sizeof(CoreOptionValue));
        if (!core_option)
            continue;
        core_entries[i].option = (void *)core_option;

        if (key)
        {
            // printf("core_entries: key: %s\n", key);
            core_option->key = (char *)calloc((strlen(key) + 1), sizeof(char));
            if (core_option->key)
                strcpy(core_option->key, key);
        }
        if (default_value)
        {
            // printf("core_entries: default_value: %s\n", default_value);
            core_option->default_value = (char *)calloc((strlen(default_value) + 1), sizeof(char));
            if (core_option->default_value)
                strcpy(core_option->default_value, default_value);
        }

        if (!values)
            continue;

        int n_values = 0;
        int geted_option_pos = 0;
        while (values[n_values].value)
        {
            // printf("core_entries: value: %s\n", values[n_values].value);
            if (!geted_option_pos && default_value && strcmp(values[n_values].value, default_value) == 0)
            {
                geted_option_pos = 1;
                core_option->pos = n_values;
            }
            n_values++;
        }

        // printf("core_entries: n_values: %d\n", n_values);

        if (n_values == 0)
            continue;
        core_option->n_options = n_values;
        core_option->values = (char **)calloc(n_values, sizeof(char *));
        if (!core_option->values)
            continue;
        core_option->labels = (char **)calloc(n_values, sizeof(char *));
        if (!core_option->labels)
            continue;

        for (j = 0; j < n_values; j++)
        {
            if (values[j].value)
            {
                core_option->values[j] = (char *)calloc((strlen(values[j].value) + 1), sizeof(char));
                if (core_option->values[j])
                    strcpy(core_option->values[j], values[j].value);
            }
            if (values[j].label)
            {
                core_option->labels[j] = (char *)calloc((strlen(values[j].label) + 1), sizeof(char));
                if (core_option->labels[j])
                    strcpy(core_option->labels[j], values[j].label);
            }
        }
        core_entries[i].enable = 1;
    }
    core_entries[n_options].desc = (char *)calloc((strlen(STR_RESET_DEFAULT_CONFIG) + 1), sizeof(char));
    if (core_entries[n_options].desc)
        strcpy(core_entries[n_options].desc, STR_RESET_DEFAULT_CONFIG);
    core_entries[n_options].type = MENU_ENTRY_TYPE_CALLBACK;
    core_entries[n_options].option = resetCoreConfig;
    core_entries[n_options].enable = 1;

    if (game_loading || game_loaded)
        loadCoreConfig(CONFIG_TYPE_GAME);
    else
        loadCoreConfig(CONFIG_TYPE_MAIN);

    return 0;
}
